package com.mall.b2c.service.impl;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Resource;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import com.mall.b2c.domain.store.StoreGoodsDomain;
import com.mall.b2c.service.*;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import com.mall.b2c.bean.GoodsBean;
import com.mall.b2c.dao.GoodsDao;
import com.mall.b2c.domain.goods.BrandDomain;
import com.mall.b2c.domain.goods.GoodsDomain;
import com.mall.b2c.domain.goods.GoodsTagDomain;
import com.mall.b2c.domain.goods.TagDomain;
import com.mall.common.bean.Message;
import com.mall.common.bean.Order;
import com.mall.common.bean.Order.Direction;
import com.mall.common.bean.Pager;
import com.mall.common.bean.SearchFilter;
import com.mall.common.hibernate.Finder;
import com.mall.common.service.impl.BaseServiceImpl;
import com.mall.common.util.Collections3;
import com.mall.common.util.DateHelper;



/**
 * @author wangzx
 * Service实现类 - 商品
 */
@Service("b2c")
@Transactional(readOnly=true)
public class GoodsServiceImpl extends BaseServiceImpl<GoodsDomain, Long> implements GoodsService {
	@Resource private TagService tagService;
	@Resource private GoodsTagService goodsTagService;
	@Resource private StoreGoodsService storeGoodsService;
	@Resource
	public void setBaseDao(GoodsDao goodsDao) { 
		super.setBaseDao(goodsDao); 
	}
	
	@Override
	@Transactional(propagation=Propagation.REQUIRED)
	public Message saveGoods(GoodsDomain goods, Long[] tagIds) {
		if(!this.isUnique("code", null, goods.getCode())) {
			Message.error("商品编号已存在!", null);
		}
		processAlbum(goods);
		this.persist(goods);
		if (ArrayUtils.isNotEmpty(tagIds)) {
			for(Long tagId : tagIds) {
				GoodsTagDomain goodsTag = new GoodsTagDomain();
				goodsTag.setGoods(goods);
				goodsTag.setTag(tagService.find(tagId));
				goodsTagService.persist(goodsTag);
			} 
		}
		return Message.success("", null);
	}

	@Transactional(propagation=Propagation.REQUIRED)
	@Override
	public Message updateGoods(GoodsDomain goods, Long[] tagIds) {
		Long goodsId = goods.getId();
		GoodsDomain currentGoods = this.find(goodsId);
		if(currentGoods == null) {
			return Message.error("当前商品不存在!", null);
		}
		if(!this.isUnique("code", currentGoods.getCode(), goods.getCode())) {
			return Message.error("商品编号已存在!", null);
		}
		BrandDomain brand = goods.getBrand();
		if(brand != null && brand.getId() == null) {
			goods.setBrand(null);
		}
		processAlbum(goods);
		this.updateByUpdater(goods);
		/**删除原来的 关联实体 */
		Set<GoodsTagDomain> goodsTags = currentGoods.getGoodsTags();
		if(!CollectionUtils.isEmpty(goodsTags)) {
			Long[] goodsTagsIds = new Long[goodsTags.size()];
			int index = 0;
			for(GoodsTagDomain goodsTag : goodsTags) {
				goodsTagsIds[index] = goodsTag.getId();
				index ++;
			}
			goodsTagService.remove(goodsTagsIds);
		}
		/**新增 现有的 关联实体 */
		if (ArrayUtils.isNotEmpty(tagIds)) {
			for(Long tagId : tagIds) {
				GoodsTagDomain goodsTag = new GoodsTagDomain();
				goodsTag.setGoods(goods);
				goodsTag.setTag(tagService.find(tagId));
				goodsTagService.persist(goodsTag);
			} 
		}
		return Message.success("", null);
	}

	@Transactional(propagation=Propagation.REQUIRED)
	@Override
	public Message updatePic(GoodsDomain goods) {
		Long goodsId = goods.getId();
		GoodsDomain currentGoods = this.find(goodsId);
		if(currentGoods == null) {
			return Message.error("当前商品不存在!", null);
		}
		processAlbum(goods);
		this.updateByUpdater(goods);
		return Message.success("", null);
	}

	/**
	 * 处理商品相册
	 */
	private void processAlbum(GoodsDomain goods) {
		String[] image_files = goods.getImage_files();
		if (ArrayUtils.isNotEmpty(image_files)) {
			StringBuilder sb = new StringBuilder();
			for (int i = 0; i < image_files.length; i++) {
				if (StringUtils.isNotBlank(image_files[i]) && !image_files[i].equals(",")) {
					sb.append(image_files[i]);
				}
				if (i < image_files.length - 1) {
					sb.append(",");
				}
			}
			goods.setImage_file(sb.toString());
		}else{
			goods.setImage_file("");
		}
	}
	

	@Override
	public List<GoodsDomain> getGoodsList(String propertyName, Object[] values) {
		if(StringUtils.isBlank(propertyName)) {
			return null;
		}
		if(ArrayUtils.isEmpty(values)) {
			return null;
		}
		Finder finder = Finder.create("from GoodsDomain where " + propertyName + " in (:propertyValues)");
		finder.setParamList("propertyValues", values);
		return finder.findHQLList();
	}
	

	@Override
	public List<GoodsDomain> findList(Long[] excludeIds, Long goodsCategoryId, String goodsCategoryCode, 
			Long brandId, Long tagId, String manufacturer, Boolean isMarketable, Integer count, boolean hasStock, Long storeId, String orderBy) {
		Finder finder = null;
		if(tagId != null){
			//TODO sql
			finder = Finder.create("select distinct bean.goods from StoreGoodsDomain bean,GoodsTagDomain gtbean where gtbean.goods.id = bean.goods.id and gtbean.tag.id = :tagId ");
			finder.setParam("tagId", tagId);
		}else{
			finder = Finder.create("select bean.goods from StoreGoodsDomain bean where 1=1");
		}

		finder.append(" and bean.store.id = :storeId");
		finder.setParam("storeId", storeId);

		if(hasStock){
			finder.append(" and bean.goods.stock>0");
		}
		if(goodsCategoryId != null){
			finder.append(" and bean.goods.goodsCategory.id = :goodsCategoryId");
			finder.setParam("goodsCategoryId", goodsCategoryId);
		}
		if(StringUtils.isNotBlank(goodsCategoryCode)){
			finder.append(" and bean.goods.goodsCategory.code like '" + goodsCategoryCode + "%'");
		}
		if(brandId != null){
			finder.append(" and bean.goods.brand.id = :brandId");
			finder.setParam("brandId", brandId);
		}
		/*if(StringUtils.isNotBlank(manufacturer)){
			finder.append(" and bean.manufacturer = :manufacturer");
			finder.setParam("manufacturer", manufacturer);
		}*/
		if(isMarketable != null){
			finder.append(" and bean.goods.isMarketable = :isMarketable");
			finder.setParam("isMarketable", isMarketable);
		}
		if(excludeIds != null){
			finder.append(" and bean.goods.id not in(:ids)");
			finder.setParamList("ids", excludeIds);
		}
		if(count != null){
			finder.setMaxResults(count);
		}
		if(StringUtils.isNotBlank(orderBy)){
			String[] strs = orderBy.split(" ");
			if(strs.length > 1){
				if(!strs[0].contains(".")){
					strs[0] = "bean.goods."+strs[0];
				}
				finder.setOrder(new Order(strs[0], Direction.valueOf(strs[1])));
			}else{
				if(!orderBy.contains(".")){
					orderBy = "bean.goods."+orderBy;
				}
				finder.setOrder(Order.desc(orderBy));
			}
		}
		return finder.findHQLList();
	}

	private CriteriaQuery<GoodsDomain> createGoodsCriteriaQuery(
			Long[] goodsCategoryIds, String[] categoryCodes, Long brandId,
			Long[] tagIds, Long[] providerIds, Boolean isMarketable,Long storeId,
			List<SearchFilter> filterList, List<Order> orderList) {
		CriteriaBuilder criteriabuilder = getEntityManager().getCriteriaBuilder();
		CriteriaQuery<GoodsDomain> criteriaQuery = criteriabuilder.createQuery(GoodsDomain.class);
		Root<GoodsDomain> root = criteriaQuery.from(GoodsDomain.class);
		criteriaQuery.select(root);
		
		Predicate predicate = criteriabuilder.conjunction();
		
		List<Predicate> orPredicates = new ArrayList<Predicate>();
		if(goodsCategoryIds != null && goodsCategoryIds.length > 0){
			Path<Long> idPath = root.get("goodsCategory").get("id");
	        for(int i=0; i<goodsCategoryIds.length; i++){
		        orPredicates.add(criteriabuilder.equal(idPath, goodsCategoryIds[i]));
	        }
		}
        
//		if(providerIds!= null && providerIds.length > 0){
//			Path<Long> idPath = root.get("provider").get("id");
//	        for(int i=0; i<providerIds.length; i++){
//		        orPredicates.add(criteriabuilder.equal(idPath, providerIds[i]));
//	        }
//		}
		
		if(categoryCodes != null && categoryCodes.length > 0){
	        Path<String> codePath = root.get("goodsCategory").get("code");
	        for(int i=0; i<categoryCodes.length; i++){
		        orPredicates.add(criteriabuilder.like(codePath, categoryCodes[i] + "%"));
	        }
		}
		
		if(Collections3.isNotEmpty(orPredicates)){
			 predicate = criteriabuilder.or(orPredicates.toArray(new Predicate[orPredicates.size()]));
		}
		
		if(tagIds != null && tagIds.length >= 0){
			List<TagDomain> tags = tagService.get(tagIds);
			if (Collections3.isNotEmpty(tags)) {
				predicate = criteriabuilder.and(predicate, root.join("goodsTags").get("tag").in(tags));
				criteriaQuery.distinct(true);
			}
		}

//		if(storeId != null){
			List<StoreGoodsDomain> stores = storeGoodsService.get(new Long[]{storeId});
			if (Collections3.isNotEmpty(stores)) {
				predicate = criteriabuilder.and(predicate, root.join("storeGoods").get("store").in(stores));
				criteriaQuery.distinct(true);
			}
		//}
		
		if(brandId != null){
			predicate = criteriabuilder.and(predicate, criteriabuilder.equal(root.get("brand").get("id"),brandId));
			criteriaQuery.distinct(true);
		}
		
		if (isMarketable != null){
			predicate = criteriabuilder.and(predicate, criteriabuilder.equal(root.get("isMarketable"), isMarketable));
		}
		
		criteriaQuery.where(predicate);
		return criteriaQuery;
	}

	@Override
	public Pager findPager(Long[] goodsCategoryIds, String[] goodsCategoryCodes,
			Long brandId, Long[] tagIds, Long[] providerIds, Boolean isMarketable,Long storeId,
			List<SearchFilter> filterList, List<Order> orderList, Pager pager) {
		CriteriaQuery<GoodsDomain> criteriaQuery = createGoodsCriteriaQuery(goodsCategoryIds, goodsCategoryCodes, brandId,
				tagIds, providerIds, isMarketable, storeId, filterList, orderList);
		return super.findByPager(criteriaQuery, pager);
	}

	@Override
	public List<GoodsDomain> findList(String categoryCode, Date beginDate,
			Date endDate, Integer first, Integer maxResults) {
		String hql = "from GoodsDomain bean where bean.isMarketable = true ";
		Finder f = Finder.create(hql);
		if(StringUtils.isNotBlank(categoryCode)){
			f.append(" and bean.goodsCategory.code like concat(:code, '%')");
			f.setParam("code", categoryCode);
		}
			
		if(beginDate != null){
			f.append(" and bean.modifyDate >= :beginDate");
			f.setParam("beginDate",beginDate);
		}
		
		if(endDate != null){
			f.append(" and bean.modifyDate <= :endDate");
			f.setParam("endDate", DateHelper.addDate(endDate, 1));
		}
		
		if(first != null){
			f.setFirstResult(first);
		}
		
		if(maxResults != null){
			f.setMaxResults(maxResults);
		}
		return f.findHQLList();
	}

	@Override
	public List<GoodsDomain> tip(String keyword) {
		if(StringUtils.isBlank(keyword)) {
			return null;
		}
		keyword = keyword.trim().toLowerCase();
		Finder finder = assembleTipQuery(keyword, Boolean.TRUE);
		finder.setMaxResults(15);
		List<GoodsDomain> goodsList = finder.findHQLList();
		if(!CollectionUtils.isEmpty(goodsList) && goodsList.size() >= 15) {
			return goodsList;
		}
		int size = goodsList.size();
		finder = assembleTipQuery(keyword, Boolean.FALSE);
		finder.setMaxResults(15 - size);
		List<GoodsDomain> gsList = finder.findHQLList();
		if(!CollectionUtils.isEmpty(gsList)) {
			goodsList.addAll(gsList);
		}
		return goodsList;
	}
	
	private Finder assembleTipQuery(String keyword, Boolean isPrefix) {
		Finder finder = Finder.create("from GoodsDomain g WHERE g.isMarketable=true ");
		if(keyword.matches("^[a-z,0-9]+$") && !keyword.matches("^[0-9]+$")) {
			if(isPrefix == Boolean.TRUE) {
				finder.append("and LOWER(g.pyName) like :goodsPyName");
				finder.setParam("goodsPyName", keyword+"%");
			} else {
				finder.append("and LOWER(g.pyName) like :goodsPyName and LOWER(g.pyName) not like :goodsPyName2");
				finder.setParam("goodsPyName", "%"+keyword+"%");
				finder.setParam("goodsPyName2", keyword+"%");
			}
		} else if(keyword.matches("^[0-9]+$")) {
			if(isPrefix == Boolean.TRUE) {
				finder.append("and g.code like :goodsCode");
				finder.setParam("goodsCode",  keyword+"%");
			} else {
				finder.append("and g.code like :goodsCode and g.code not like :goodsCode2");
				finder.setParam("goodsCode", "%"+keyword+"%");
				finder.setParam("goodsCode2", keyword+"%");
			}
		} else {
			keyword = keyword.replace("%", "@%");
			if(isPrefix == Boolean.TRUE) {
				finder.append("and g.name like :goodsName escape '@'");
				finder.setParam("goodsName", keyword+"%");
			} else {
				finder.append("and g.name like :goodsName escape '@' and g.name not like :goodsName2 escape '@'");
				finder.setParam("goodsName", "%"+keyword+"%");
				finder.setParam("goodsName2", keyword+"%");
			}
		}
		finder.append(" group by g.name order by (case WHEN g.stock is null THEN 0 WHEN g.stock > 0 THEN 1 ELSE 0 END) desc, g.priority desc");
		return finder;
	}

	@Override
	public List<GoodsBean> getHistoryGoods(String idsStr) {
		if(StringUtils.isNotBlank(idsStr)){
			Pattern p = Pattern.compile(",(\\d+,){1,10}");
			Matcher m = p.matcher(idsStr);
			if(m.find()){
				Finder finder = Finder.create("select new com.mall.b2c.bean.GoodsBean(bean) from GoodsDomain bean where bean.isMarketable=true and bean.id in (-1").append(m.group(0)).append("-1)");
				finder.setMaxResults(10);
				return finder.findHQLList();
			}
		}
		return null;
	}

	@Override
	public void upMarket(Long[] ids) {
		StringBuilder hql = new StringBuilder();
		hql.append("update GoodsDomain set isMarketable = :isMarketable where id in (");
		hql.append(StringUtils.join(ids, ","));
		hql.append(")");
		getEntityManager().createQuery(hql.toString()).setParameter("isMarketable", true).executeUpdate();
	}

	@Override
	public void downMarket(Long[] ids) {
		StringBuilder hql = new StringBuilder();
		hql.append("update GoodsDomain set isMarketable = :isMarketable where id in (");
		hql.append(StringUtils.join(ids, ","));
		hql.append(")");
		getEntityManager().createQuery(hql.toString()).setParameter("isMarketable", false).executeUpdate();
	}
}
